using System;
using SharpMud.Server.Systems;

namespace SharpMud.MudLib.SAMPLE.Entities
{
#if !Use_Common_MudLib
	/// <summary>
	/// The PlayerMetabase class. Manages saving and loading of players.
	/// </summary>
	public class PlayerMetabase
	{
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType);

		#region Private Variables
		private MudLib.SAMPLE.MudLibRoot			_HostServerApp;
		private PlayerSystem					    _HostPlayerSystem;
		private System.Collections.ArrayList	_LoadedPlayers;
		#endregion

		#region Public Properties
		/// <summary>
		/// Gets a player from the metabase, loading it if neccesary. The player will NOT
		/// be 'initialized' into the mud world before being returned. Null is returned if
		/// the player cannot be found.
		/// </summary>
		public PlayerCharacter this[string playerName]
		{
			get
			{
				return this.GetPlayer(playerName,true,false);
			}
		}

		/// <summary>
		/// The hosting Server Application of this PlayerMetabase instance.
		/// </summary>
		public MudLib.SAMPLE.MudLibRoot HostServerApp
		{
			get
			{
				return _HostServerApp;
			}
		}
		/// <summary>
		/// The hosting Player System of this PlayerMetabase instance.
		/// </summary>
		public PlayerSystem HostPlayerSystem
		{
			get
			{
				return _HostPlayerSystem;
			}
		}
		/// <summary>
		/// The total number of players that are loaded and registered with this PlayerMetabase.
		/// </summary>
		public int TotalPlayersLoaded
		{
			get
			{
				return _LoadedPlayers.Count;
			}
		}
        /// <summary>
        /// The total number of player files within this PlayerMetabase's directory scope.
        /// </summary>
		public int TotalPlayerFiles
		{
			get
			{
				string[] playerFileNames = System.IO.Directory.GetFiles(this.RootPlayerFolder,this.PlayerFileNamePattern);
				return playerFileNames.Length;
			}
		}

		#endregion

		#region Public Methods
		/// <summary>
		/// Adds a player into the Player Metabase.
		/// Will automatically save the player.
		/// </summary>
		/// <param name="player">The Player to add into the Player Metabase</param>
		/// <exception cref="PlayerExistsException">Thrown when trying to add a player that already exists in the Player Metabase, loaded or not.</exception>
		public void AddPlayer(PlayerCharacter player)
		{
			if(this.PlayerExists(player.PlayerName))
			{
				throw new PlayerExistsException(player.PlayerName);
			}
			this._LoadedPlayers.Add(player);
			this.SavePlayer(player.PlayerName);
		}
		/// <summary>
		/// Deletes a player. The player must not be currently loaded.
		/// </summary>
		/// <param name="playerName">The name of the player to delete.</param>
		/// <exception cref="PlayerLoadedException">Thrown if the player is currently loaded.</exception>
		public void DeletePlayer(string playerName)
		{
			if(this.PlayerIsLoaded(playerName))
			{
				throw new PlayerLoadedException(playerName);
			}
			throw new NotImplementedException();
		}

		/// <summary>
		/// Close all open players, calling respective Dispose() methods
		/// </summary>
		/// <param name="saveOpenPlayers"></param>
		public void CloseAllPlayers(bool saveOpenPlayers)
		{
			if(!saveOpenPlayers)
			{
				log.Warn("Closing open players... (not saving)");
			}
			else
			{
				log.Info("Saving and closing open players...");
			}
				foreach(PlayerCharacter pc in (System.Collections.ArrayList)this._LoadedPlayers.Clone())
				{
					ClosePlayer(pc.PlayerName,saveOpenPlayers);	
				}
			if(!saveOpenPlayers)
			{
				log.Warn("Done closing open players.");
			}
			else
			{
				log.Info("Done closing and saving open players.");
			}
		}
		/// <summary>
		/// Loads all players into the metabase, allowing specification as to whether or not to
		/// overwrite already loaded players. Allows specification as to whether or not to 
		/// 'initialize' the loaded players into the mud world.
		/// </summary>
		/// <param name="overwriteLoadedPlayers">Wether or not to overwrite currently loaded players</param>
		/// <param name="initializeNewPlayers">Whether or not to 'initialize' loaded players into the mud world</param>
		public void LoadAllPlayers(bool overwriteLoadedPlayers, bool initializeNewPlayers)
		{
			string[] playerFiles = System.IO.Directory.GetFiles(RootPlayerFolder,PlayerFileNamePattern);
			foreach(string playerFile in playerFiles)
			{
				PlayerCharacter pc=null;
				try
				{
					pc = LoadPlayerFile(playerFile);
				}
				catch(System.Exception e)
				{
					log.Error("Error loading player:" + System.Environment.NewLine 
						+ " File:      \t\"" + playerFile + "\"",e);
					continue;
				}
				if(PlayerIsLoaded(pc.PlayerName))
				{
					//The player is already loaded! What do we do?
					if(overwriteLoadedPlayers==true)
					{
						//Player is already loaded, so close without saving
						ClosePlayer(pc.PlayerName,false);
						//Add the loaded player into the list of loaded players
						this._LoadedPlayers.Add(pc);
						pc.Initialize(this);
					}
					else
					{
						//This player is already loaded, and overwriting is disabled
						//So go on and load the next player and forget this one
						continue;
					}
				}
				else
				{
					//The player wasn't already loaded, so go on and toss it
					//into the list of loaded players
					this._LoadedPlayers.Add(pc);
					if(initializeNewPlayers==true)
						pc.Initialize(this);
				}
			}
		}

		/// <summary>
		/// Closes a player and tells it to de-register itself from the world.
		/// </summary>
		/// <param name="playerName">The name of the player to close</param>
		/// <param name="saveFirst">Whether or not to save the player before closing it</param>
		public void ClosePlayer(string playerName, bool saveFirst)
		{
			if(PlayerIsLoaded(playerName)==false)
			{
				throw new PlayerNotFoundException(playerName);
			}
			
			PlayerCharacter pc = this.GetPlayer(playerName,false,false);

			if(saveFirst)
			{
				this.SavePlayer(playerName);
			}
			this._LoadedPlayers.Remove(pc);
			pc.Dispose();
		}

		/// <summary>
		/// This method determines if a player exists, either loaded in memory or on disk
		/// </summary>
		/// <param name="playerName">The name of the player to look for</param>
		/// <returns>Returns True if the player exists, otherwise False.</returns>
		public bool PlayerExists(string playerName)
		{
			if(playerName==String.Empty) throw new ArgumentNullException("playerName");
			if(PlayerIsLoaded(playerName) || PlayerFileExists(playerName))
				return true;
			else
				return false;
		}
		/// <summary>
		/// Determines if a player is loaded into memory
		/// </summary>
		/// <param name="playerName">The name of the player to look for</param>
		/// <returns>Returns True if the player is loaded, otherwise False.</returns>
		public bool PlayerIsLoaded(string playerName)
		{
			if(playerName==String.Empty) throw new ArgumentNullException("playerName");
			foreach(Entities.PlayerCharacter pc in _LoadedPlayers)
			{
				if(pc.PlayerName.Trim().ToLower()==playerName.Trim().ToLower())
					return true;
			}
			return false;
		}
		/// <summary>
		/// Loads a player into memory, but will fail if player is already loaded. If the
		/// player must be loaded, it will NOT be 'initialized' into the mud world.
		/// </summary>
		/// <param name="playerName">The name of the player to load</param>
		public void LoadPlayer(string playerName)
		{
			this.LoadPlayer(playerName,false,false);
		}
		/// <summary>
		/// Loads a player into memory
		/// </summary>
		/// <param name="playerName">The name of the player to load</param>
		/// <param name="overwriteLoadedPlayer">Whether or not to overwrite an already loaded player</param>
		/// <remarks>May not be very good if a loaded player is overwritten. Naturally object references to the
		/// old player will keep the old player data alive in the system. So overwrite-functionality is still
		/// rather experimental.</remarks>
		public void LoadPlayer(string playerName, bool overwriteLoadedPlayer, bool initializeLoadedPlayer)
		{
			if(PlayerFileExists(playerName)==false)
				throw new PlayerNotFoundException(playerName);

			if(PlayerIsLoaded(playerName) && overwriteLoadedPlayer==false)
				throw new PlayerLoadedException(playerName);

			if(PlayerIsLoaded(playerName) && overwriteLoadedPlayer==true)
			{
				this.ClosePlayer(playerName,false);
			}

            PlayerCharacter pc = LoadPlayerFile(
				MudLibRoot.GetSubfileIntelligently(
				this.RootPlayerFolder,
				PlayerFileNameFromPlayerName(playerName)
				));

            this._LoadedPlayers.Add(pc);
			if(initializeLoadedPlayer)
			{
				pc.Initialize(this);
			}
		}
		/// <summary>
		/// This method takes a player name, and determines if an appropriatly named file exists
		/// for that player.
		/// </summary>
		/// <param name="playerName">The name of the player whose file to look for.</param>
		/// <returns>Returns True if the player's file exists, otherwise returns False.</returns>
		public bool PlayerFileExists(string playerName)
		{
			string ffp = MudLibRoot.GetSubfileIntelligently(
				this.RootPlayerFolder,
				PlayerFileNameFromPlayerName(playerName) );
			return System.IO.File.Exists(ffp);
		}

		/// <summary>
		/// This function determines what an appropriately named player file would be for
		/// a given player's name. Neither the player nor the file needs to exist.
		/// </summary>
		/// <param name="playerName">The name of a player whose file name is to be returned.</param>
		/// <returns>Returns a player file name.</returns>
		public string PlayerFileNameFromPlayerName(string playerName)
		{
			return String.Format(PlayerFileNameFormat,playerName.Trim().ToLower());
		}

		/// <summary>
		/// Takes a loaded player and saves its data.
		/// </summary>
		/// <param name="playerName">The name of the player to save.</param>
		/// <exception cref="PlayerNotFoundException">Thrown if the player does not exist.</exception>
		public void SavePlayer(string playerName)
		{
			if(!PlayerExists(playerName))
			{
				throw new PlayerNotFoundException(playerName);
			}

			string pfn = MudLibRoot.GetSubfileIntelligently(
				this.RootPlayerFolder,
				PlayerFileNameFromPlayerName(playerName)
				);

			SavePlayerFile(pfn,this.GetPlayer(playerName));
		}

		/// <summary>
		/// Gets a player. Sounds simple, but isn't.
		/// Will automatically load a player if it exists but isn't loaded.
		/// Will NOT 'initialize' the player before returning it.
		/// </summary>
		/// <param name="playerName">The name of the player to get.</param>
		/// <returns>Returns a player if it exists, otherwise returns Null.</returns>
		public PlayerCharacter GetPlayer(string playerName)
		{
			return GetPlayer(playerName,true,false);
		}

		/// <summary>
		/// Gets a player. Sounds simple, but isn't.
		/// Allows specification as to whether or not to load the player if it isn't loaded.
		/// Will NOT 'initialize' the player before returning it.
		/// </summary>
		/// <param name="playerName">The name of the player to get.</param>
		/// <param name="loadUnloadedPlayer">Whether or not to load the player if it isn't loaded but it does exist.</param>
		/// <returns>If the player was retrieved (one way or another) then it is returned, otherwise Null is returned.</returns>
		public PlayerCharacter GetPlayer(string playerName, bool loadUnloadedPlayer)
		{
			return GetPlayer(playerName,loadUnloadedPlayer);
		}

		/// <summary>
		/// Gets a player. Sounds simple, but isn't.
		/// Allows specification as to whether or not to load the player if it isn't loaded.
		/// Allows specification as to whether or not to 'initalize' the player before returning it.
		/// </summary>
		/// <param name="playerName">The name of the player to get.</param>
		/// <param name="loadUnloadedPlayer">Whether or not to load the player if it isn't loaded but does exist.</param>
		/// <param name="initializeUnloadedPlayer">Whether or not to 'initialize' the player after loading but before returning it.</param>
		/// <returns>Returns the player in question if it exists or was loaded. If not loaded, or it doesn't exist, returns Null.</returns>
		public PlayerCharacter GetPlayer(string playerName, bool loadUnloadedPlayer, bool initializeUnloadedPlayer)
		{
			if(PlayerIsLoaded(playerName))
			{
				foreach(PlayerCharacter pc in _LoadedPlayers)
				{
					if(pc.PlayerName.Trim().ToLower()==playerName.Trim().ToLower())
					{
						return pc;
					}
				}
			}
			else if(!PlayerIsLoaded(playerName) && PlayerFileExists(playerName) && loadUnloadedPlayer)
			{
				this.LoadPlayer(playerName,false,initializeUnloadedPlayer);
			}
			
			//Player neither found nor loaded
			return null;
		}
		#endregion

		#region File System Tie-Ins
		/// <summary>
		/// The format of a Player file's name.
		/// </summary>
		public string PlayerFileNameFormat
		{
			get
			{
				return "{0}.SampleMudlibPlayer.XML";
			}
		}
		/// <summary>
		/// The pattern that will match Player files.
		/// </summary>
		public string PlayerFileNamePattern
		{
			get
			{
				return PlayerFileNameFormat.Replace("{0}","*");
			}
		}
		/// <summary>
		/// The root folder where all the Player files should be kept.
		/// </summary>
		public string RootPlayerFolder
		{
			get
			{
				return this.HostPlayerSystem.RootPlayerFolder;
			}
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Default constructor. Does nothing.
		/// </summary>
		private PlayerMetabase()
		{
		}
		/// <summary>
		/// Constructor for the PlayerMetabase class.
		/// </summary>
		public PlayerMetabase(MudLib.SAMPLE.MudLibRoot hostServerApp, PlayerSystem hostPlayerSystem)
		{
			_HostServerApp = hostServerApp;
			_HostPlayerSystem = hostPlayerSystem;
			_LoadedPlayers = new System.Collections.ArrayList();
		}
		/// <summary>
		/// Constructor for the PlayerMetabase class.
		/// </summary>
		public PlayerMetabase(MudLib.SAMPLE.MudLibRoot hostServerApp)
            :this(hostServerApp,hostServerApp.PlayerSystem)
		{
		}
		/// <summary>
		/// Constructor for the PlayerMetabase class.
		/// </summary>
		public PlayerMetabase(PlayerSystem hostPlayerSystem)
			:this(hostPlayerSystem.Host,hostPlayerSystem)
		{
		}
		#endregion

		#region Explicit Player Loading/Saving
		/// <summary>
		/// Uses XML deserialization to load a PlayerCharacter object from a given file.
		/// </summary>
		/// <param name="fullPlayerFileName">The file to load the PlayerCharacter from.</param>
		/// <returns>Returns a PlayerCharacter object.</returns>
		public static PlayerCharacter LoadPlayerFile(string fullPlayerFileName)
		{
			System.IO.Stream fs = new System.IO.FileStream(fullPlayerFileName,System.IO.FileMode.Open);

			System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(
				typeof(PlayerCharacter));

			PlayerCharacter pc = (PlayerCharacter)xs.Deserialize(fs);

			fs.Close();

			pc.TurnOffCreation();

			return pc;
		}
		/// <summary>
		/// Uses XML serialization to save a PlayerCharacter object to a given file.
		/// </summary>
		/// <param name="fullPlayerFileName">The file to save the PlayerCharacter to.</param>
		/// <param name="player">The PlayerCharacter object to save.</param>
		public static void SavePlayerFile(string fullPlayerFileName, PlayerCharacter player)
		{
			System.Xml.Serialization.XmlSerializer xs = new System.Xml.Serialization.XmlSerializer(
				player.GetType() );

			System.IO.FileStream fs = new System.IO.FileStream(fullPlayerFileName,System.IO.FileMode.Create);

			xs.Serialize(fs,player);

			fs.Close();
		}
		#endregion
	}
#endif
}
